Next | Prev | Up | Top | Contents | Index
The Multiprocessor Environment
A multiprocessor has two or more CPU modules, all of the same type. The CPUs execute independently, but all share the same main memory. Any CPU can execute the code of the IRIX kernel, and it is common for two or more CPUs to be executing kernel code, including driver code, simultaneously.
Uniprocessor Assumptions
The original UNIX architecture assumed a uniprocessor hardware environment with a hierarchy of interrupt levels. Ordinary code could be preempted by an interrupt, but an interrupt handler could only be preempted by an interrupt at a higher level.
This assumed hardware environment was reflected in the design of device drivers and kernel support functions.
- In a uniprocessor, an upper-half driver entry point such as pfxopen() cannot be preempted except by an interrupt. It has exclusive access to driver variables except for those changed by the interrupt handler.
- Once in an interrupt handler, no other code can possibly execute except an interrupt of a higher hardware level. The interrupt handler has exclusive access to driver variables.
- The interrupt handler can use kernel functions such as splhi() to set the hardware interrupt mask, blocking interrupts of all kinds, and thus getting exclusive access to all memory including kernel data structures.
All of these assumptions fail in a multiprocessor.
- Upper-half entry points can be entered concurrently on multiple CPUs. For example, one CPU can be executing pfxopen() while another CPU is in pfxstrategy(). Exclusive use of driver variables cannot be assumed.
- An interrupt can be taken on one CPU while upper-half routines or a timeout function execute concurrently on other CPUs. The interrupt routine cannot assume exclusive use of driver variables.
- Interrupt-level functions such as splhi() are meaningless, since at best they set the interrupt mask on the current CPU only. Other CPUs can accept interrupts at all levels. The interrupt handler can never gain exclusive access to kernel data.
The process of making a driver multiprocessor-ready consists of changing all code whose correctness depends on uniprocessor assumptions.
Protecting Common Data
Whenever a common resource can be updated by two processes concurrently, the resource must be protected by a lock that represents the exclusive right to update the resource. Before changing the resource, the software acquires the lock, claiming exclusive access. After changing the resource, the software releases the lock.
The IRIX kernel provides a set of functions for creating and using locks. It provides another set of functions for creating and using semaphore objects, which are like locks but sometimes more flexible. Both sets of functions are discussed under "Waiting and Mutual Exclusion".
Sleeping and Waking
Sometimes the lock is not available--some other process executing in another CPU has acquired the lock. When this happens, the requesting process is delayed in the lock function until the lock is free. To delay, or sleep, is allowed for upper-half entry points, because they execute (in effect) as subroutines of user processes.
Interrupt handlers and timeout functions are not permitted to sleep. They have no process identity and so there is no mechanism for saving and restoring their state. An interrupt handler can test a lock, and can claim the lock conditionally, but if a lock is already held, the handler must have some alternate way of storing data.
Next | Prev | Up | Top | Contents | Index